﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Emerald
{
    public class BoardArray
    {
        private Tile[,] internalArray = new Tile[21, 20];
        public BoardPoint TopLeftBoundary = new BoardPoint(255, 255);
        public BoardPoint BottomRightBoundary = new BoardPoint(0, 0);

        public Tile this[BoardPoint bp]
        {
            get { return this[bp.X, bp.Y]; }
            set { this[bp.X, bp.Y] = value; }
        }

        public Tile this[byte x, byte y]
        {
            get
            {
                if (IsTile(new BoardPoint(x, y), 0, 0) == false)
                    return null;
                return internalArray[x, y];
            }
            set
            {
                if (x > internalArray.GetLength(0) || y > internalArray.GetLength(1))
                    throw new Exception();

                if (internalArray[x, y] != null && value != null)
                    throw new Exception();

                if (internalArray[x, y] == null && value == null)
                    throw new Exception();

                internalArray[x, y] = value;

                if (value == null) return;

                internalArray[x, y].Location = new BoardPoint(x, y);

                byte xMin = Math.Min(x, TopLeftBoundary.X);
                byte yMin = Math.Min(y, TopLeftBoundary.Y);
                byte xMax = Math.Max(x, BottomRightBoundary.X);
                byte yMax = Math.Max(y, BottomRightBoundary.Y);

                TopLeftBoundary = new BoardPoint(xMin, yMin);
                BottomRightBoundary = new BoardPoint(xMax, yMax);
            }
        }

        public Tile[] MovableTiles
        {
            get
            {
                Tile[] movableTiles = new Tile[8];
                int index = 0;
                for (byte x = TopLeftBoundary.X; x <= BottomRightBoundary.X; x++)
                {
                    for (byte y = TopLeftBoundary.Y; y <= BottomRightBoundary.Y; y++)
                    {
                        if (internalArray[x, y] != null && internalArray[x, y].Piece == null && GetNeighboursOf(new BoardPoint(x, y)).Count(a => a != null) <= 2)
                            movableTiles[index++] = internalArray[x, y];
                    }
                }
                return movableTiles;
            }
        }

        public Tile[] Tiles
        {
            get
            {
                Tile[] tiles = new Tile[20];
                int index = 0;
                for (int x = TopLeftBoundary.X; x <= BottomRightBoundary.X; x++)
                {
                    for (int y = TopLeftBoundary.Y; y <= BottomRightBoundary.Y; y++)
                    {
                        if (internalArray[x, y] != null)
                            tiles[index++] = internalArray[x, y];
                    }
                }
                return tiles;
            }
        }

        public Piece[] Pieces
        {
            get
            {
                Piece[] pieces = new Piece[12];
                int index = 0;
                Tile[] tiles = Tiles;
                for (int i = 0; i < tiles.Length; i++)
                {
                    Tile tile = tiles[i];
                    if (tile == null)
                        break;
                    if (tile.Piece != null)
                        pieces[index++] = tile.Piece;
                }
                return pieces;
            }
        }

        public int CountPieces(EColor color)
        {
            int i = 0;
            Piece[] pieces = Pieces;
            for (int index = 0; index < pieces.Length; index++)
            {
                Piece piece = pieces[index];
                if (piece == null)
                    break;
                if (piece.Color == color)
                    i++;
            }
            return i;
        }

        internal Tile[] GetFilledNeighboursOf(BoardPoint bp)
        {
            Tile[] neighbours = new Tile[4];
            int index = 0;
            if (IsFilledTile(bp, -1, 0)) neighbours[index++] = internalArray[bp.X - 1, bp.Y];
            if (IsFilledTile(bp, +1, 0)) neighbours[index++] = internalArray[bp.X + 1, bp.Y];
            if (IsFilledTile(bp, 0, +1)) neighbours[index++] = internalArray[bp.X, bp.Y + 1];
            if (IsFilledTile(bp, 0, -1)) neighbours[index++] = internalArray[bp.X, bp.Y - 1];
            return neighbours;
        }

        internal Tile[] GetNeighboursOf(BoardPoint bp)
        {
            Tile[] neighbours = new Tile[4];
            int index = 0;
            if (IsTile(bp, -1, 0)) neighbours[index++] = internalArray[bp.X - 1, bp.Y];
            if (IsTile(bp, +1, 0)) neighbours[index++] = internalArray[bp.X + 1, bp.Y];
            if (IsTile(bp, 0, +1)) neighbours[index++] = internalArray[bp.X, bp.Y + 1];
            if (IsTile(bp, 0, -1)) neighbours[index++] = internalArray[bp.X, bp.Y - 1];
            return neighbours;
        }

        internal Tile[] GetAllFilledNeighboursOf(BoardPoint bp)
        {
            Tile[] neighbours = new Tile[8];
            int index = 0;
            if (IsFilledTile(bp, -1, 0)) neighbours[index++] = internalArray[bp.X - 1, bp.Y];
            if (IsFilledTile(bp, +1, 0)) neighbours[index++] = internalArray[bp.X + 1, bp.Y];
            if (IsFilledTile(bp, 0, +1)) neighbours[index++] = internalArray[bp.X, bp.Y + 1];
            if (IsFilledTile(bp, 0, -1)) neighbours[index++] = internalArray[bp.X, bp.Y - 1];
            if (IsFilledTile(bp, -1, -1)) neighbours[index++] = internalArray[bp.X - 1, bp.Y - 1];
            if (IsFilledTile(bp, +1, -1)) neighbours[index++] = internalArray[bp.X + 1, bp.Y - 1];
            if (IsFilledTile(bp, +1, +1)) neighbours[index++] = internalArray[bp.X + 1, bp.Y + 1];
            if (IsFilledTile(bp, -1, +1)) neighbours[index++] = internalArray[bp.X - 1, bp.Y + 1];
            return neighbours;
        }

        internal Tile[] GetNeighboursOf(BoardPoint bp, params BoardPoint[] excluding)
        {
            Tile[] tiles = GetNeighboursOf(bp);
            int index = 0;
            Tile[] neighbours = new Tile[tiles.Length];
            for (int i = 0; i < tiles.Length; i++)
            {
                Tile neighbour = tiles[i];
                if (neighbour == null)
                    break;
                if (false == excluding.Contains(neighbour.Location))
                    neighbours[index++] = neighbour;
            }
            return neighbours;
        }

        internal Tile[] GetFilledNeighboursOf(BoardPoint bp, EColor color)
        {
            Tile[] tiles = GetAllFilledNeighboursOf(bp);
            int index = 0;
            Tile[] neighbours = new Tile[tiles.Length];

            for (int i = 0; i < tiles.Length; i++)
            {
                Tile tile = tiles[i];
                if (tile == null)
                    break;
                if (tile.Piece != null &&
                    tile.Piece.Color == color &&
                    tile.Piece.State == EState.Marked)
                {
                    neighbours[index++] = tile;
                }
            }
            return neighbours;
        }

        internal bool IsFilledTile(BoardPoint bp, sbyte relX, sbyte relY)
        {
            return bp.X + relX >= 0 &&
                   bp.Y + relY >= 0 &&
                   bp.X + relX < 21 &&
                   bp.Y + relY < 20 &&
                   internalArray[bp.X + relX, bp.Y + relY] != null &&
                   internalArray[bp.X + relX, bp.Y + relY].Piece != null;
        }

        internal bool IsTile(BoardPoint bp, sbyte relX, sbyte relY)
        {
            return bp.X + relX >= 0 &&
                   bp.Y + relY >= 0 &&
                   bp.X + relX < 21 &&
                   bp.Y + relY < 20 &&
                   internalArray[bp.X + relX, bp.Y + relY] != null;
        }

        internal bool IsFilledTile(BoardPoint bp)
        {
            return IsFilledTile(bp, 0, 0);
        }

        internal Tile[] GetMarkedTiles(EColor color)
        {
            Tile[] tiles = Tiles;
            int index = 0;
            Tile[] movables = new Tile[tiles.Length];
            for (int i = 0; i < tiles.Length; i++)
            {
                Tile tile = tiles[i];
                if (tile == null)
                    break;
                if (tile.Piece != null &&
                    tile.Piece.Color == color &&
                    tile.Piece.State == EState.Marked)
                {
                    movables[index++] = tile;
                }
            }
            return movables;
        }

        internal bool ContainsMarkedPiece(byte x, byte y, EColor color)
        {
            return
                (x >= 0 &&
                   y >= 0 &&
                   x < 21 &&
                   y < 20) &&

                (internalArray[x, y] != null &&
                    internalArray[x, y].Piece != null &&
                    internalArray[x, y].Piece.Color == color &&
                    internalArray[x, y].Piece.State == EState.Marked);
        }

        internal EColor? GetColor(byte x, byte y)
        {
            if (!(x >= 0 &&
                   y >= 0 &&
                   x < 21 &&
                   y < 20))
                return null;

            Tile tile = internalArray[x, y];

            if (tile != null && tile.Piece != null)
            {
                return tile.Piece.Color;
            }

            return null;
        }

        public void FitBoundaries()
        {
            Tile[] tiles = Tiles;
            TopLeftBoundary = new BoardPoint(255, 255);
            BottomRightBoundary = new BoardPoint(0, 0);

            byte xMin = byte.MaxValue;
            byte yMin = byte.MaxValue;
            byte xMax = byte.MinValue;
            byte yMax = byte.MinValue;
            foreach (Tile tile in tiles)
            {
                xMin = Math.Min(tile.Location.X, xMin);
                yMin = Math.Min(tile.Location.Y, yMin);
                xMax = Math.Max(tile.Location.X, xMax);
                yMax = Math.Max(tile.Location.Y, yMax);
            }

            TopLeftBoundary = new BoardPoint(xMin, yMin);
            BottomRightBoundary = new BoardPoint(xMax, yMax);
        }
    }
}
